{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NCaqjtgaEadC"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "eEDRAwTKCVfE"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "64hxAKUC2yuO"
      },
      "source": [
        "# Keras の再帰型ニューラルネットワーク（RNN）"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HQioQ8OkTyLa"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/rnn\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\"> TensorFlow.orgで表示</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/guide/keras/rnn.ipynb\">     <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colabで実行</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/guide/keras/rnn.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub でソースを表示{</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/guide/keras/rnn.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dYgB0yQF3KPB"
      },
      "source": [
        "## はじめに\n",
        "\n",
        "再帰型ニューラルネットワーク（RNN）は、時系列や自然言語などのシーケンスデータのモデリングを強力に行うニューラルネットワークのクラスです。\n",
        "\n",
        "概略的には、RNN レイヤーは `for` ループを使用してシーケンスのタイムステップをイテレートし、それまでに確認したタイムステップに関する情報をエンコードする内部状態を維持します。\n",
        "\n",
        "Keras RNN API は、次に焦点を当てて設計されています。\n",
        "\n",
        "- **使いやすさ**: `keras.layers.RNN`、`keras.layers.LSTM`、`keras.layers.GRU` レイヤーがビルトインされているため、難しい構成選択を行わずに、再帰型モデルを素早く構築できます。\n",
        "\n",
        "- **カスタマイズしやすさ**: カスタムビヘイビアを持つ独自の RNN セルレイヤーを構築でき（`for` ループの内部）、一般的な `keras.layers.RNN` レイヤー（`for` ループ自体）で使用することができます。このため、異なるリサーチアイデアを最小限のコードで柔軟に素早くプロトタイプすることができます。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jQVvj8gyHvJ5"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "r5CjARa6VgYT"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LkxKP3b17LzN"
      },
      "source": [
        "## ビルトイン RNN レイヤー: 単純な例"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "efTGph2DKzF3"
      },
      "source": [
        "Keras には、次の 3 つのビルトイン RNN レイヤーがあります。\n",
        "\n",
        "1. `keras.layers.SimpleRNN`: 前のタイムステップの出力が次のタイムステップにフィードされる、完全に連結された RNN です。\n",
        "\n",
        "2. `keras.layers.GRU`: [Cho et al., 2014](https://arxiv.org/abs/1406.1078) で初めて提案されたレイヤー。\n",
        "\n",
        "3. `keras.layers.LSTM`: [Hochreiter & Schmidhuber, 1997](https://www.bioinf.jku.at/publications/older/2604.pdf) で初めて提案されたレイヤー。\n",
        "\n",
        "2015 年始めに、Keras に、LSTM および GRU の再利用可能なオープンソース Python 実装が導入されました。\n",
        "\n",
        "整数のシーケンスを処理し、そのような整数を 64 次元ベクトルに埋め込み、`LSTM` レイヤーを使用してベクトルのシーケンスを処理する `Sequential` モデルの単純な例を次に示しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Al1mieW7pcBN"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential()\n",
        "# Add an Embedding layer expecting input vocab of size 1000, and\n",
        "# output embedding dimension of size 64.\n",
        "model.add(layers.Embedding(input_dim=1000, output_dim=64))\n",
        "\n",
        "# Add a LSTM layer with 128 internal units.\n",
        "model.add(layers.LSTM(128))\n",
        "\n",
        "# Add a Dense layer with 10 units.\n",
        "model.add(layers.Dense(10))\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WzeoOXJFl8tr"
      },
      "source": [
        "ビルトイン RNN は、多数の有益な特徴をサポートしています。\n",
        "\n",
        "- `dropout` および `recurrent_dropout` 引数を介した再帰ドロップアウト\n",
        "- `go_backwards` 引数を介して、入力シーケンスを逆順に処理する能力\n",
        "- `unroll` 引数を介したループ展開（CPU で短いシーケンスを処理する際に大幅な高速化が得られる）\n",
        "- 他にも多くの特徴があります。\n",
        "\n",
        "詳細については、「[RNN API ドキュメント](https://keras.io/api/layers/recurrent_layers/)」を参照してください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "faufeitWY9iN"
      },
      "source": [
        "## 出力と状態\n",
        "\n",
        "デフォルトでは、RNN レイヤーの出力には、サンプル当たり 1 つのベクトルが含まれます。このベクトルは、最後のタイムステップに対応する RNN セル出力で、入力シーケンス全体の情報が含まれます。この出力の形状は `(batch_size, units)` で、`units` はレイヤのコンストラクタに渡される `units` 引数に対応します。\n",
        "\n",
        "RNN レイヤーは、`return_sequences=True` に設定した場合、各サンプルに対する出力のシーケンス全体（サンプル当たりのタイムステップごとの 1 ベクトル）を返すこともできます。この出力の形状は `(batch_size, timesteps, units)` です。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "skdeA64WSXad"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential()\n",
        "model.add(layers.Embedding(input_dim=1000, output_dim=64))\n",
        "\n",
        "# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)\n",
        "model.add(layers.GRU(256, return_sequences=True))\n",
        "\n",
        "# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)\n",
        "model.add(layers.SimpleRNN(128))\n",
        "\n",
        "model.add(layers.Dense(10))\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ShDSX0cX6JuG"
      },
      "source": [
        "さらに、RNN レイヤーはその最終内部状態を返すことができます。返された状態は、後で RNN 実行を再開する際に使用するか、[別の RNN を初期化するため](https://arxiv.org/abs/1409.3215)に使用できます。この設定は通常、エンコード・デコーダ方式の Sequence-to-Sequence モデルで使用され、エンコーダの最終状態がデコーダの初期状態として使用されます。\n",
        "\n",
        "内部状態を返すように RNN レイヤーを構成するには、レイヤーを作成する際に、`return_state` パラメータを `True` に設定します。`LSTM` には状態テンソルが 2 つあるのに対し、`GRU` には 1 つしかないことに注意してください。\n",
        "\n",
        "レイヤーの初期状態を構成するには、追加のキーワード引数 `initial_state` を使ってレイヤーを呼び出します。次の例に示すように、状態の形状は、レイヤーのユニットサイズに一致する必要があることに注意してください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JMFm4HpfRCgv"
      },
      "outputs": [],
      "source": [
        "encoder_vocab = 1000\n",
        "decoder_vocab = 2000\n",
        "\n",
        "encoder_input = layers.Input(shape=(None,))\n",
        "encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(\n",
        "    encoder_input\n",
        ")\n",
        "\n",
        "# Return states in addition to output\n",
        "output, state_h, state_c = layers.LSTM(64, return_state=True, name=\"encoder\")(\n",
        "    encoder_embedded\n",
        ")\n",
        "encoder_state = [state_h, state_c]\n",
        "\n",
        "decoder_input = layers.Input(shape=(None,))\n",
        "decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(\n",
        "    decoder_input\n",
        ")\n",
        "\n",
        "# Pass the 2 states to a new LSTM layer, as initial state\n",
        "decoder_output = layers.LSTM(64, name=\"decoder\")(\n",
        "    decoder_embedded, initial_state=encoder_state\n",
        ")\n",
        "output = layers.Dense(10)(decoder_output)\n",
        "\n",
        "model = keras.Model([encoder_input, decoder_input], output)\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kMnBuhsHopK9"
      },
      "source": [
        "## RNN レイヤーと RNN セル\n",
        "\n",
        "ビルトイン RNN レイヤーのほかに、RNN API は、セルレベルの API も提供しています。入力シーケンスの全バッチを処理する RNN レイヤーとは異なり、RNN セルは単一のタイムステップのみを処理します。\n",
        "\n",
        "セルは、RNN レイヤーの `for` ループ内にあります。`keras.layers.RNN` レイヤー内のセルをラッピングすることで、シーケンスのバッチを処理できるレイヤー（`RNN(LSTMCell(10))`）を得られます。\n",
        "\n",
        "数学的には、`RNN(LSTMCell(10))` は `LSTM(10)` と同じ結果を出します。実際、TF v1.x でのこのレイヤーの実装は、対応する RNN セルを作成し、それを RNN レイヤーにラッピングするだけでした。ただし、ビルトインの `GRU` と `LSTM` レイヤーを使用すると、CuDNN が使用できるようになり、パフォーマンスの改善を確認できることがあります。\n",
        "\n",
        "ビルトイン RNN セルには 3 つあり、それぞれ、それに一致する RNN レイヤーに対応しています。\n",
        "\n",
        "- `keras.layers.SimpleRNNCell` は `SimpleRNN` レイヤーに対応します。\n",
        "\n",
        "- `keras.layers.GRUCell` は `GRU` レイヤーに対応します。\n",
        "\n",
        "- `keras.layers.LSTMCell` は `LSTM` レイヤーに対応します。\n",
        "\n",
        "セルの抽象化とジェネリックな `keras.layers.RNN` クラスを合わせることで、リサーチ用のカスタム RNN アーキテクチャの実装を簡単に行えるようになります。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2mEPlBZbkMUh"
      },
      "source": [
        "## バッチ間のステートフルネス\n",
        "\n",
        "非常に長い（無限の可能性のある）シーケンスを処理する場合は、**バッチ間ステートフルネス**のパターンを使用するとよいでしょう。\n",
        "\n",
        "通常、RNN レイヤーの内部状態は、新しいバッチが確認されるたびにリセットされます（レイヤーが確認する各サンプルは、過去のサンプルから独立したものだと考えられます）。レイヤーは、あるサンプルを処理する間のみ状態を維持します。\n",
        "\n",
        "非常に長いシーケンスがある場合、より短いシーケンスに分割し、レイヤーの状態をリセットせずにそれらの短いシーケンスを順次、RNN レイヤーにフィードすることができます。こうすると、レイヤーはサブシーケンスごとに確認していても、シーケンス全体の情報を維持することができます。\n",
        "\n",
        "これは、コンストラクタに `stateful=True` を設定して行います。\n",
        "\n",
        "シーケンス `s = [t0, t1, ... t1546, t1547]` があるとした場合、これを次のように分割します。\n",
        "\n",
        "```\n",
        "s1 = [t0, t1, ... t100] s2 = [t101, ... t201] ... s16 = [t1501, ... t1547]\n",
        "```\n",
        "\n",
        "そして、次のようにして処理します。\n",
        "\n",
        "```python\n",
        "lstm_layer = layers.LSTM(64, stateful=True) for s in sub_sequences:   output = lstm_layer(s)\n",
        "```\n",
        "\n",
        "状態をクリアする場合は、`layer.reset_states()` を使用できます。\n",
        "\n",
        "> 注意: このセットアップでは、あるバッチのサンプル `i` は前のバッチのサンプル `i` の続きであることを前提としています。つまり、すべてのバッチには同じ数のサンプル（バッチサイズ）が含まれることになります。たとえば、バッチに `[sequence_A_from_t0_to_t100,  sequence_B_from_t0_to_t100]` が含まれるとした場合、次のバッチには、`[sequence_A_from_t101_to_t200,  sequence_B_from_t101_to_t200]` が含まれます。\n",
        "\n",
        "完全な例を次に示します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6EMJTalJ7uLC"
      },
      "outputs": [],
      "source": [
        "paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "\n",
        "lstm_layer = layers.LSTM(64, stateful=True)\n",
        "output = lstm_layer(paragraph1)\n",
        "output = lstm_layer(paragraph2)\n",
        "output = lstm_layer(paragraph3)\n",
        "\n",
        "# reset_states() will reset the cached state to the original initial_state.\n",
        "# If no initial_state was provided, zero-states will be used by default.\n",
        "lstm_layer.reset_states()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S6qLMeccQ9dP"
      },
      "source": [
        "### RNN 状態の再利用\n",
        "\n",
        "<a id=\"rnn_state_reuse\"></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yIBKaOjz2tMh"
      },
      "source": [
        "RNN の記録済みの状態は、`layer.weights()` には含まれません。RNN レイヤーの状態を再利用する場合は、`layer.states` によって状態の値を取得し、`new_layer(inputs, initial_state=layer.states)` などの Keras Functional API またはモデルのサブクラス化を通じて新しいレイヤーの初期状態として使用することができます。\n",
        "\n",
        "この場合には、単一の入力と出力を持つレイヤーのみをサポートする Sequential モデルを使用できない可能性があることにも注意してください。このモデルでは追加入力としての初期状態を使用することができません。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9OC1hdDPTqlH"
      },
      "outputs": [],
      "source": [
        "paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)\n",
        "\n",
        "lstm_layer = layers.LSTM(64, stateful=True)\n",
        "output = lstm_layer(paragraph1)\n",
        "output = lstm_layer(paragraph2)\n",
        "\n",
        "existing_state = lstm_layer.states\n",
        "\n",
        "new_lstm_layer = layers.LSTM(64)\n",
        "new_output = new_lstm_layer(paragraph3, initial_state=existing_state)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iihZ7SmcM0nv"
      },
      "source": [
        "## 双方向性 RNN\n",
        "\n",
        "時系列以外のシーケンスについては（テキストなど）、開始から終了までのシーケンスを処理だけでなく、逆順に処理する場合、RNN モデルの方がパフォーマンスに優れていることがほとんどです。たとえば、ある文の次の単語を予測するには、その単語の前に出現した複数の単語だけでなく、その単語に関する文脈があると役立ちます。\n",
        "\n",
        "Keras は、そのような双方向性のある RNN を構築するために、`keras.layers.Bidirectional` ラッパーという簡単な API を提供しています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "nH1fet6rORHu"
      },
      "outputs": [],
      "source": [
        "model = keras.Sequential()\n",
        "\n",
        "model.add(\n",
        "    layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))\n",
        ")\n",
        "model.add(layers.Bidirectional(layers.LSTM(32)))\n",
        "model.add(layers.Dense(10))\n",
        "\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4gLl2G0fUrIW"
      },
      "source": [
        "内部的には、`Bidirectional` は渡された RNN レイヤーをコピーし、新たにコピーされたレイヤーの `go_backwards` フィールドを転換して、入力が逆順に処理されるようにします。\n",
        "\n",
        "`Bidirectional` RNN の出力は、デフォルトで、フォワードレイヤー出力とバックワードレイヤー出力の総和となります。これとは異なるマージ動作が必要な場合は（連結など）、`Bidirectional` ラッパーコンストラクタの `merge_mode` パラメータを変更します。`Bidirectional` の詳細については、[API ドキュメント](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Bidirectional/)を参照してください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lnem4BM8L6fC"
      },
      "source": [
        "## パフォーマンス最適化と CuDNN カーネル\n",
        "\n",
        "TensorFlow 2.0 では、ビルトインの LSTM と GRU レイヤーは、GPU が利用できる場合にデフォルトで CuDNN カーネルを活用するように更新されています。この変更により、以前の `keras.layers.CuDNNLSTM/CuDNNGRU` レイヤーは使用廃止となったため、実行するハードウェアを気にせずにモデルを構築することができます。\n",
        "\n",
        "CuDNN カーネルは、特定の前提を以って構築されており、レイヤーは**ビルトイン LSTM または GRU レイヤーのデフォルト値を変更ない場合は CuDNN カーネルを使用できません**。これらには次のような例があります。\n",
        "\n",
        "- `activation` 関数を `tanh` からほかのものに変更する。\n",
        "- `recurrent_activation` 関数を `sigmoid` からほかのものに変更する。\n",
        "- `recurrent_dropout` > 0 を使用する。\n",
        "- `unroll` を True に設定する。LSTM/GRU によって内部 `tf.while_loop` は展開済み `for` ループに分解されます。\n",
        "- `use_bias` を False に設定する。\n",
        "- 入力データが厳密に右詰でない場合にマスキングを使用する（マスクが厳密に右詰データに対応している場合でも、CuDNN は使用されます。これは最も一般的な事例です）。\n",
        "\n",
        "制約の詳細については、[LSTM](https://www.tensorflow.org/api_docs/python/tf/keras/layers/LSTM/) および [GRU](https://www.tensorflow.org/api_docs/python/tf/keras/layers/GRU/) レイヤーのドキュメントを参照してください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AFGAR8iHXYbe"
      },
      "source": [
        "### 利用できる場合に CuDNN カーネルを使用する\n",
        "\n",
        "パフォーマンスの違いを確認するために、単純な LSTM モデルを構築してみましょう。\n",
        "\n",
        "入力シーケンスとして、MNIST 番号の行のシーケンスを使用し（ピクセルの各行をタイムステップとして扱います）、番号のラベルを予測します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aWhkHxcFFbhu"
      },
      "outputs": [],
      "source": [
        "batch_size = 64\n",
        "# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).\n",
        "# Each input sequence will be of size (28, 28) (height is treated like time).\n",
        "input_dim = 28\n",
        "\n",
        "units = 64\n",
        "output_size = 10  # labels are from 0 to 9\n",
        "\n",
        "# Build the RNN model\n",
        "def build_model(allow_cudnn_kernel=True):\n",
        "    # CuDNN is only available at the layer level, and not at the cell level.\n",
        "    # This means `LSTM(units)` will use the CuDNN kernel,\n",
        "    # while RNN(LSTMCell(units)) will run on non-CuDNN kernel.\n",
        "    if allow_cudnn_kernel:\n",
        "        # The LSTM layer with default options uses CuDNN.\n",
        "        lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))\n",
        "    else:\n",
        "        # Wrapping a LSTMCell in a RNN layer will not use CuDNN.\n",
        "        lstm_layer = keras.layers.RNN(\n",
        "            keras.layers.LSTMCell(units), input_shape=(None, input_dim)\n",
        "        )\n",
        "    model = keras.models.Sequential(\n",
        "        [\n",
        "            lstm_layer,\n",
        "            keras.layers.BatchNormalization(),\n",
        "            keras.layers.Dense(output_size),\n",
        "        ]\n",
        "    )\n",
        "    return model\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "19CbiJx9jSqX"
      },
      "source": [
        "MNIST データセットを読み込みましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9vtPhdr4dsLs"
      },
      "outputs": [],
      "source": [
        "mnist = keras.datasets.mnist\n",
        "\n",
        "(x_train, y_train), (x_test, y_test) = mnist.load_data()\n",
        "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
        "sample, sample_label = x_train[0], y_train[0]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MyTLP1qceEhX"
      },
      "source": [
        "モデルのインスタンスを作成してトレーニングしましょう。\n",
        "\n",
        "`sparse_categorical_crossentropy` をモデルの損失関数として選択します。モデルの出力形状は `[batch_size, 10]` です。モデルのターゲットは整数ベクトルで、各整数は 0 から 9 の範囲内にあります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5V23fJqvNpfv"
      },
      "outputs": [],
      "source": [
        "model = build_model(allow_cudnn_kernel=True)\n",
        "\n",
        "model.compile(\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "    optimizer=\"sgd\",\n",
        "    metrics=[\"accuracy\"],\n",
        ")\n",
        "\n",
        "\n",
        "model.fit(\n",
        "    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fZJEigasi2nU"
      },
      "source": [
        "では、CuDNN カーネルを使用しないモデルと比較してみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "usiGCoLB70Ot"
      },
      "outputs": [],
      "source": [
        "noncudnn_model = build_model(allow_cudnn_kernel=False)\n",
        "noncudnn_model.set_weights(model.get_weights())\n",
        "noncudnn_model.compile(\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        "    optimizer=\"sgd\",\n",
        "    metrics=[\"accuracy\"],\n",
        ")\n",
        "noncudnn_model.fit(\n",
        "    x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cEsHBOZx8nq1"
      },
      "source": [
        "NVIDIA GPU と CuDNN がインストールされたマシンで実行すると、CuDNN で構築されたモデルの方が、通常の TensorFlow カーネルを使用するモデルに比べて非常に高速に実行されます。\n",
        "\n",
        "CPU のみの環境で推論を実行する場合でも、同じ CuDNN 対応モデルを使用できます。次の `tf.device` 注釈は単にデバイスの交換を強制しています。GPU が利用できないな場合は、デフォルトで CPU で実行されます。\n",
        "\n",
        "実行するハードウェアを気にする必要がなくなったのです。素晴らしいと思いませんか？"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mi62NdlxdWbJ"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "\n",
        "with tf.device(\"CPU:0\"):\n",
        "    cpu_model = build_model(allow_cudnn_kernel=True)\n",
        "    cpu_model.set_weights(model.get_weights())\n",
        "    result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)\n",
        "    print(\n",
        "        \"Predicted result is: %s, target result is: %s\" % (result.numpy(), sample_label)\n",
        "    )\n",
        "    plt.imshow(sample, cmap=plt.get_cmap(\"gray\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zKWBP5lcNMTA"
      },
      "source": [
        "## リスト/辞書入力、またはネストされた入力による RNN\n",
        "\n",
        "ネストされた構造では、インプルメンターは単一のタイムステップにより多くの情報を含めることができます。たとえば、動画のフレームに、音声と動画の入力を同時に含めることができます。この場合のデータ形状は、次のようになります。\n",
        "\n",
        "`[batch, timestep, {\"video\": [height, width, channel], \"audio\": [frequency]}]`\n",
        "\n",
        "別の例では、手書きのデータに、現在のペンの位置を示す座標 x と y のほか、筆圧情報も含めることができます。データは次のように表現できます。\n",
        "\n",
        "`[batch, timestep, {\"location\": [x, y], \"pressure\": [force]}]`\n",
        "\n",
        "次のコードは、このような構造化された入力を受け入れるカスタム RNN セルの構築方法を例に示しています。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GuPzwT4xa7D0"
      },
      "source": [
        "### ネストされた入力/出力をサポートするカスタムセルを定義する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "r2TARZ5mh0Ny"
      },
      "source": [
        "独自レイヤーの記述に関する詳細は、「[サブクラス化による新規レイヤとモデルの作成](https://www.tensorflow.org/guide/keras/custom_layers_and_models/)」を参照してください。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f30iKy2LpdLa"
      },
      "outputs": [],
      "source": [
        "class NestedCell(keras.layers.Layer):\n",
        "    def __init__(self, unit_1, unit_2, unit_3, **kwargs):\n",
        "        self.unit_1 = unit_1\n",
        "        self.unit_2 = unit_2\n",
        "        self.unit_3 = unit_3\n",
        "        self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]\n",
        "        self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]\n",
        "        super(NestedCell, self).__init__(**kwargs)\n",
        "\n",
        "    def build(self, input_shapes):\n",
        "        # expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]\n",
        "        i1 = input_shapes[0][1]\n",
        "        i2 = input_shapes[1][1]\n",
        "        i3 = input_shapes[1][2]\n",
        "\n",
        "        self.kernel_1 = self.add_weight(\n",
        "            shape=(i1, self.unit_1), initializer=\"uniform\", name=\"kernel_1\"\n",
        "        )\n",
        "        self.kernel_2_3 = self.add_weight(\n",
        "            shape=(i2, i3, self.unit_2, self.unit_3),\n",
        "            initializer=\"uniform\",\n",
        "            name=\"kernel_2_3\",\n",
        "        )\n",
        "\n",
        "    def call(self, inputs, states):\n",
        "        # inputs should be in [(batch, input_1), (batch, input_2, input_3)]\n",
        "        # state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]\n",
        "        input_1, input_2 = tf.nest.flatten(inputs)\n",
        "        s1, s2 = states\n",
        "\n",
        "        output_1 = tf.matmul(input_1, self.kernel_1)\n",
        "        output_2_3 = tf.einsum(\"bij,ijkl-&gt;bkl\", input_2, self.kernel_2_3)\n",
        "        state_1 = s1 + output_1\n",
        "        state_2_3 = s2 + output_2_3\n",
        "\n",
        "        output = (output_1, output_2_3)\n",
        "        new_states = (state_1, state_2_3)\n",
        "\n",
        "        return output, new_states\n",
        "\n",
        "    def get_config(self):\n",
        "        return {\"unit_1\": self.unit_1, \"unit_2\": unit_2, \"unit_3\": self.unit_3}\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dWWVWikJLOJY"
      },
      "source": [
        "### ネストされた入力/出力で RNN モデルを構築する\n",
        "\n",
        "上記で定義した `keras.layers.RNN` レイヤーとカスタムセルを使用する Keras モデルを構築しましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NziLqoBsjgvG"
      },
      "outputs": [],
      "source": [
        "unit_1 = 10\n",
        "unit_2 = 20\n",
        "unit_3 = 30\n",
        "\n",
        "i1 = 32\n",
        "i2 = 64\n",
        "i3 = 32\n",
        "batch_size = 64\n",
        "num_batches = 10\n",
        "timestep = 50\n",
        "\n",
        "cell = NestedCell(unit_1, unit_2, unit_3)\n",
        "rnn = keras.layers.RNN(cell)\n",
        "\n",
        "input_1 = keras.Input((None, i1))\n",
        "input_2 = keras.Input((None, i2, i3))\n",
        "\n",
        "outputs = rnn((input_1, input_2))\n",
        "\n",
        "model = keras.models.Model([input_1, input_2], outputs)\n",
        "\n",
        "model.compile(optimizer=\"adam\", loss=\"mse\", metrics=[\"accuracy\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "oOlmAaBV9EqJ"
      },
      "source": [
        "### ランダムに生成されたデータでモデルをトレーニングする\n",
        "\n",
        "このモデルに適した候補データセットを持ち合わせていないため、ランダムな Numpy データを使って実演することにします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "lcNzFSUhrLD0"
      },
      "outputs": [],
      "source": [
        "input_1_data = np.random.random((batch_size * num_batches, timestep, i1))\n",
        "input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))\n",
        "target_1_data = np.random.random((batch_size * num_batches, unit_1))\n",
        "target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))\n",
        "input_data = [input_1_data, input_2_data]\n",
        "target_data = [target_1_data, target_2_data]\n",
        "\n",
        "model.fit(input_data, target_data, batch_size=batch_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wraOdp6MCadQ"
      },
      "source": [
        "Keras `keras.layers.RNN` レイヤーでは、シーケンス内の個別のステップの数学ロジックを定義することだけが期待されています。シーケンスのイテレーションは、`keras.layers.RNN` レイヤーによって処理されます。新しいタイプの RNN（LSTM など） を素早くプロトタイプ化する上で、非常に強力な方法です。\n",
        "\n",
        "詳細については、[API ドキュメント](https://https://www.tensorflow.org/api_docs/python/tf/keras/layers/RNN/)を参照してください。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "rnn.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
